package sourcecode;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class Plugin implements InvocationHandler {

    private final Object target;

    private final Interceptor interceptor;

    private final Map<Class<?>, Set<Method>> signatureMap;

    public Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
    }

    public static Object wrap(Object target, Interceptor interceptor) throws NoSuchMethodException {
        // 获取插件类 Intercepts 注解上的接口，方法，存储 map容器
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        // 数组存储：在 map 容器中所包含原生类的接口
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
            // JDK动态代理
            return Proxy.newProxyInstance(type.getClassLoader(),
                    interfaces,
                    new Plugin(target, interceptor, signatureMap));
        }
        return target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 代理类执行对应 map 容器所包含的执行方法
        Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        if (methods != null && methods.contains(method)) {
            return interceptor.intercept(new Invocation(target, method, args));
        }
        // 否则直接调用，不对原生方法进行增强
        return method.invoke(target, args);
    }

    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) throws NoSuchMethodException {
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        Signature[] signatures = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
        for (Signature signature : signatures) {
            Method method = signature.type().getMethod(signature.method(), signature.args());
            Set<Method> methods = signatureMap.computeIfAbsent(signature.type(), key -> new HashSet<>());
            methods.add(method);
        }
        return signatureMap;
    }

    private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
        Set<Class<?>> interfaces = new HashSet<>();
        while (type != null) {
            for (Class<?> c : type.getInterfaces()) {
                if (signatureMap.containsKey(c)) {
                    interfaces.add(c);
                }
            }
            type = type.getSuperclass();
        }
        return interfaces.toArray(new Class<?>[interfaces.size()]);
    }
}

